package ru.ksu.ktk.diploma.sdi.search;
import org.xmldb.api.modules.XPathQueryService;
import org.xmldb.api.base.*;
import org.xmldb.api.DatabaseManager;

import java.io.*;
import static java.lang.Class.forName;
import java.util.*;
import java.net.MalformedURLException;

import com.hp.hpl.jena.ontology.*;
import com.hp.hpl.jena.rdf.model.*;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;
import com.hp.hpl.jena.vocabulary.OWL;
import com.hp.hpl.jena.vocabulary.RDFS;

public class IndexBuilder
{
    static XPathQueryService xpathToCollection;
    static Database database;
    static org.xmldb.api.base.Collection dataCollection;
    private OntModel datawarehouse;
    private OntModel rusaat;
    public IndexBuilder()
    {

    }

    public static void main(String[] args)
    {
        init();
        System.out.println("exist init OK.");

        /*
           // построение индекса
        IndexBuilder vrmIndex = new IndexBuilder();

       vrmIndex.loadDW();
       System.out.println("DW OK.");

       vrmIndex.loadRUSAAT();
       System.out.println("RUSAAT OK.");

       vrmIndex.buildIndex();
       System.out.println("index build OK.");

       */

        // обновление индекса
        IndexBuilder emkuIndex = new IndexBuilder();

        emkuIndex.loadIncrementDW();
        System.out.println("Increment DW OK.");

        emkuIndex.loadRUSAAT();
        System.out.println("RUSAAT OK.");

        emkuIndex.updateIndex();
        System.out.println("index update OK.");
    }

    public static void init()
    {
        existInit();
    }
    // инициируем СУБД eXist
    private static void existInit()
    {
        String existDriver = "org.exist.xmldb.DatabaseImpl";
        try {
            Class cl = forName(existDriver);
            database = (Database)cl.newInstance();
            DatabaseManager.registerDatabase(database);
            dataCollection = DatabaseManager.getCollection(
                    "xmldb:exist://localhost:8080/exist/xmlrpc/db/mom");
            xpathToCollection = (XPathQueryService) dataCollection.getService("XPathQueryService", "1.0");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (XMLDBException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

    // загрузка хранилища фактов
    private void loadDW()
    {
        this.datawarehouse = ModelFactory.createOntologyModel(OntModelSpec.OWL_LITE_MEM);
        this.datawarehouse.read(SearchConstants.existCollectionURI +
                SearchConstants.dataWarehouse, "RDF/XML-ABBREV");
    }

    // загрузка нового хранилища фактов
    private void loadIncrementDW()
    {
        this.datawarehouse = ModelFactory.createOntologyModel(OntModelSpec.OWL_LITE_MEM);
        this.datawarehouse.read(SearchConstants.existCollectionURI +
                SearchConstants.dataWarehouseIncrement, "RDF/XML-ABBREV");
        this.datawarehouse.loadImports();
    }

    // загрузка тезауруса ААТ
    private void loadRUSAAT()
    {
        this.rusaat = ModelFactory.createOntologyModel(OntModelSpec.OWL_LITE_MEM);
        this.rusaat.read(SearchConstants.existCollectionURI +
                SearchConstants.existThesaurusRUSAAT, "RDF/XML-ABBREV");
    }

    // выполнение запроса
    public OntModel executeQuery(ArrayList<Resource> descriptors)
    {
        HashMap<String, OntModel> models = ProcessThesaurusQuery(descriptors, this.datawarehouse);
        return ApplyProductionOnModels(new ArrayList<OntModel>(models.values()), "INTERSECT");
    }

    // обработка запроса с помощью тезауруса
    public HashMap<String, OntModel> ProcessThesaurusQuery(ArrayList<Resource> descriptors, OntModel dw)
    {
        HashMap<String, OntModel> resModels = new HashMap<String, OntModel>();
        for(Resource thesTerm : descriptors)
        {
            ArrayList<Resource> extendedTermList = new ArrayList<Resource>();
            ArrayList<Property> forbiddenProps = fillForbiddenProperties(dw);
            ArrayList<Resource> forbiddenResources = new ArrayList<Resource>();
            ArrayList<Statement> forbiddenTransitions = new ArrayList<Statement>();
            extendedTermList.clear();

            ArrayList<Resource> impliedTerms = digForImpliedTerms(thesTerm, dw);

            impliedTerms.add(thesTerm);
            for(Resource t : impliedTerms)
            {
                extendedTermList.add(t);
            }
            OntModel res = ModelFactory.createOntologyModel(OntModelSpec.OWL_LITE_MEM);
            res = digForResources(dw, extendedTermList , forbiddenResources,
                    forbiddenProps, forbiddenTransitions, 0, res);

            resModels.put(thesTerm.getURI(), res);
        }
        return resModels;
    }

    // определение списка запрещенных свойств, т.е. свойств, по которым
    // запрещены переходы при построении окрестности
    public  ArrayList<Property>  fillForbiddenProperties(InfModel source)
    {
        ArrayList<Property> forbiddenProps = new ArrayList<Property>();
        forbiddenProps.add(OWL.allValuesFrom );
        forbiddenProps.add(RDFS.domain );
        forbiddenProps.add(RDFS.subClassOf );
        forbiddenProps.add(source.getProperty("http://cidoc.ics.forth.gr#P00F_"));
        forbiddenProps.add(source.getProperty("http://cidoc.ics.forth.gr#P00B_"));
        forbiddenProps.add(source.getProperty("http://cidoc.ics.forth.gr#P127B_"));
        forbiddenProps.add(source.getProperty("http://cidoc.ics.forth.gr#P127F_"));
        forbiddenProps.add(source.getProperty("http://cidoc.ics.forth.gr#P11F_"));
        forbiddenProps.add(source.getProperty("http://cidoc.ics.forth.gr#P12F_"));
        forbiddenProps.add(source.getProperty("http://cidoc.ics.forth.gr#P11B_"));
        forbiddenProps.add(source.getProperty("http://cidoc.ics.forth.gr#P12B_"));
        //   forbiddenProps.add(RDFS.label );
        //   forbiddenProps.add(RDFS.comment );
        forbiddenProps.add(OWL.inverseOf );
        forbiddenProps.add(OWL.onProperty );
        return forbiddenProps;
    }

    // выполнение формулы заданного типа над списком онтологий
    private OntModel ApplyProductionOnModels(ArrayList<OntModel> models, String formulaType)
    {
        OntModel resIntersected = ModelFactory.createOntologyModel();
        if(formulaType.equals("INTERSECT"))
        {
            if(models.size() > 2)
            {
                resIntersected = ModelFactory.createOntologyModel(OntModelSpec.OWL_LITE_MEM,
                        multipleIntersection(models.get(0), models.subList(1, models.size())));
            }
            else
            if (models.size() == 2)
            {
                resIntersected = ModelFactory.createOntologyModel(OntModelSpec.OWL_LITE_MEM,
                        models.get(0).getBaseModel().intersection(models.get(1).getBaseModel()));
            }
            else if (models.size() == 1)
            {
                resIntersected = models.get(0);
            }
        }
        return resIntersected;
    }

    // поиск теминов, находящихся НИЖЕ в иерархии понятия thesTerm
    private ArrayList<Resource> digForImpliedTerms(Resource thesTerm, Model source)
    {
        ArrayList<Resource> res = new ArrayList<Resource>();
        OntModel sub = ModelFactory.createOntologyModel(OntModelSpec.OWL_LITE_MEM, source);
        StmtIterator stIter = sub.listStatements(null,
                sub.getProperty("http://cidoc.ics.forth.gr#P127B_"), thesTerm);

        while(stIter.hasNext())
        {
            Statement st = stIter.nextStatement();
//            System.out.println(st.getSubject() + " imlpies " + thesTerm.getLocalName());
            res.addAll(digForImpliedTerms(st.getSubject(), source));
            res.add(st.getSubject());
        }

        stIter = sub.listStatements(thesTerm,
                sub.getProperty("http://cidoc.ics.forth.gr#P127F_"), (RDFNode)null);

        while(stIter.hasNext())
        {
            Statement st = stIter.nextStatement();
//            System.out.println(st.getObject() + " imlpies " + thesTerm.getLocalName());
            res.addAll(digForImpliedTerms((Resource)st.getObject(), source));
            res.add((Resource)st.getObject());
        }

        return res;
    }

    // применение опрератора "пересечения" над списком онтологий
    private Model multipleIntersection(OntModel currentModel, List<OntModel> modelsTail)
    {
        if(modelsTail.size() > 1)
        {
            return currentModel.getBaseModel().intersection(
                    (multipleIntersection(
                            modelsTail.get(0), modelsTail.subList(1, modelsTail.size()))));
        }
        else
        {
            if (modelsTail.size() == 1)
            {
                return currentModel.getBaseModel().intersection(modelsTail.get(0).getBaseModel());
            }
            else
            {
                return currentModel;
            }
        }
    }

    // построение окрестности заданного множества терминов terms
    public OntModel digForResources(Model source, ArrayList<Resource> terms,
                                    ArrayList<Resource> forbiddenTerms,
                                    ArrayList<Property> forbiddenProperties,
                                    ArrayList<Statement> forbiddenStatements,
                                    int level, OntModel resultModel)
    {
        ArrayList<Resource> newTerms = new ArrayList<Resource>();
        ArrayList<Property> newForbiddenProps = new ArrayList<Property>();

//        newForbiddenProps.add(source.getProperty("http://cidoc.ics.forth.gr#P11F_"));
//        newForbiddenProps.add(source.getProperty("http://cidoc.ics.forth.gr#P12F_"));
//        newForbiddenProps.add(source.getProperty("http://cidoc.ics.forth.gr#P11B_"));
//        newForbiddenProps.add(source.getProperty("http://cidoc.ics.forth.gr#P12B_"));

        newForbiddenProps.add(source.getProperty("http://cidoc.ics.forth.gr#P00F_"));
        newForbiddenProps.add(source.getProperty("http://cidoc.ics.forth.gr#P00B_"));
        newForbiddenProps.add(source.getProperty("http://cidoc.ics.forth.gr#P127B_"));
        newForbiddenProps.add(source.getProperty("http://cidoc.ics.forth.gr#P127F_"));
        for(Resource term : terms)
        {
            if( ! forbiddenTerms.contains(term) )
            {
                forbiddenTerms.add(term);
                StmtIterator stIter = source.listStatements(term, null, (RDFNode)null);
//                System.out.println(" *** FOREWARD *** level = " + level);
                while(stIter.hasNext())
                {
                    Statement st = stIter.nextStatement();
                    if( ! forbiddenProperties.contains(st.getPredicate()) )
                    {
                        if( ! st.getPredicate().getLocalName().equals("type") &&
                                ! st.getPredicate().getLocalName().equals("label") )
                        {
                            newForbiddenProps.add(st.getPredicate());
                        }
                        RDFNode o = st.getObject();
                        if(o.isURIResource())
                        {
                            if( !st.getPredicate().getLocalName().equals("type"))
                            {
                                newTerms.add((Resource)o);
                            }
//                            System.out.println(" 1. URI res " + st.getSubject() +  " : " + st.getPredicate() +  " : " + o);
                            if (resultModel == null)
                            {
                                resultModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_LITE_MEM);
                            }
                            resultModel.add(st);
                        }
                        else if(o.isLiteral())
                        {
//                            System.out.println(" 1. Literal " + st.getSubject() +  " : " + st.getPredicate() +  " : " + o);
                            if (resultModel == null)
                            {
                                resultModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_LITE_MEM);
                            }
                            resultModel.add(st);
                        }
                        else if (o.isAnon())
                        {
                            newTerms.add((Resource)o);
//                            System.out.println(" anon " + o + " : " + st.getPredicate() + " : " + st.getObject());
                            if (resultModel == null)
                            {
                                resultModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_LITE_MEM);
                            }
                            resultModel.add(st);
                        }
                    }
                    else
                    {
//                        System.out.println(" ! prop forb !" + st.getSubject() + " : " +
//                                st.getPredicate() + " : "+st.getObject());
                    }
                }
//                System.out.println(" *** +++ *** " + level);
                stIter = source.listStatements(null, null, term);
//                System.out.println(" *** BACKWARD *** level = " + level);
                while(stIter.hasNext())
                {
                    Statement st = stIter.nextStatement();
                    if( ! forbiddenProperties.contains(st.getPredicate()))
                    {
                        if( ! st.getPredicate().getLocalName().equals("type") &&
                                ! st.getPredicate().getLocalName().equals("label") )
                        {
                            newForbiddenProps.add(st.getPredicate());
                        }

                        Resource o = st.getSubject();
                        if( !st.getPredicate().getLocalName().equals("type"))
                        {
                            newTerms.add(o);
                        }
//                        System.out.println(" 2. URI res " + o + " : " + st.getPredicate() + " : " + st.getObject());
                        if (resultModel == null)
                        {
                            resultModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_LITE_MEM);
                        }
                        resultModel.add(st);
                    }
                    else
                    {
//                        System.out.println(" ! prop forb !" + st.getSubject() + " : " +
//                                st.getPredicate() + " : " + st.getObject());
                    }
                }
//                System.out.println(" *** ---- *** " + level);
            }
            else
            {
//                System.out.println(" ! term forb ! " + term);
            }
        }

        // итерируем, пока не обойдем ближайшие 5 свойств
        if(newTerms.size() > 0 && level <= 5)
        {
            digForResources(source, newTerms, forbiddenTerms, newForbiddenProps,
                    forbiddenStatements, level += 1, resultModel);
        }
        return resultModel;
    }

    // построение полного индекса на все понятия тезауруса
    public void buildIndex()
    {
//        P127F_ - отношение "ВЫШЕ РОД - НИЖЕ ВИД" между понятиями тезауруса
        ObjectProperty P127F_ = rusaat.getObjectProperty(SearchConstants.crmNS + "P127F_");
        OntModel o = rusaat;
        ExtendedIterator instances = o.listIndividuals();
        ArrayList<Resource> allAATTerms = new ArrayList<Resource>(instances.toList());
        ArrayList<Resource> descriptors = new ArrayList<Resource>();
        System.out.println(allAATTerms.size());
        for (Resource i : allAATTerms)
        {
            if(! isInIndex(i))
            {   // если в индексе нет файла для понятия i
                if(i.getURI().startsWith(SearchConstants.rusaatNS) && ! isProblemDescriptor(i))
                {
                    if(i.hasProperty(P127F_))
                    {
                        if(isNarrowTermInIndex(i, P127F_))
                        {
                            descriptors.clear();
                            descriptors.add(i);
//                            System.out.println(i.getLocalName() + i.getLabel("ru"));
                            HashMap<String, OntModel> models =
                                    ProcessThesaurusQuery(descriptors, this.datawarehouse);
                            OntModel res = models.get(i.getURI());
                            String filename = "TMP/index/" + i.getLocalName();
                            File f = new File(filename + ".owl");
                            try
                            {
                                FileWriter fw = new FileWriter(f);
                                setAllPrefixes(res);
                                res.write(fw, "RDF/XML-ABBREV", f.toURL().toString());
                                fw.close();
                            } catch (IOException e)
                            {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
    }

     public void updateIndex()
     {
         ArrayList<Resource> allAATTerms;
         OntModel o = datawarehouse;
         ResIterator instances = o.listSubjects();
         allAATTerms = new ArrayList<Resource>(instances.toList());

         ArrayList<Resource> descriptors = new ArrayList<Resource>();
         System.out.println(allAATTerms.size());
         for (Resource i : allAATTerms)
         {
             if(i.getURI().startsWith(SearchConstants.rusaatNS) && ! isProblemDescriptor(i))
             {
                 descriptors.clear();
                 descriptors.add(i);
                 System.out.println(i.getLocalName() + i.getProperty(RDFS.label));
                 HashMap<String, OntModel> models =
                         ProcessThesaurusQuery(descriptors, this.datawarehouse);

                 OntModel res = models.get(i.getURI());
                 if(isEmptyModel(res))
                 {
                     System.out.println("empty term");
                     continue;
                 }

                 if(! RetrieveModelFromIndex(i).listStatements().toSet().containsAll(
                         res.listStatements().toSet()) || !isInIndex(i))
                 {
                     String filename = "TMP/update/" + i.getLocalName();
                     File f = new File(filename + ".owl");
                     String idxPath = "TMP/index/" + i.getLocalName();
                     File fi = new File(idxPath + ".owl");
                     if(!f.exists())
                     {
                         // обновить индекс = объединить новые утверждения (res)
                         // со старыми (RetrieveModelFromIndex(i))
                         res.add(RetrieveModelFromIndex(i));
                         try
                         {
                             FileWriter fw = new FileWriter(f);
                             setAllPrefixes(res);

                             res.write(fw, "RDF/XML-ABBREV", fi.toURL().toString());
                             fw.close();
                         } catch (IOException e)
                         {
                             e.printStackTrace();
                         }
                     }
                 }
                 else
                 {
                     System.out.println("the same document !");
                 }
             }
         }

         File dir = new File("TMP/update");
         for(File f : dir.listFiles())
         {

             File newFile = new File("TMP/index/" + f.getName());
             newFile.delete();
             System.out.print("storing " + f.getName() + "...");
             if(f.renameTo(newFile))
             {
                 System.out.println("ok.");
             }
             else
             {
                 System.out.println("fail.");
             }
         }
     }

    // --------------------------------------------------------------------------------

    private void setAllPrefixes(OntModel o)
    {
        o.setNsPrefix("rusaat", SearchConstants.rusaatNS);
        o.setNsPrefix("crm", SearchConstants.crmNS);
        o.setNsPrefix("emku", "http://emku.ksu.ru#") ;
        o.setNsPrefix("vrm", "http://www.museum.ru#");
        o.setNsPrefix("nmrt", "http://www.tatar.museum.ru#");
        o.setNsPrefix("rbm", "http://www.rbm.ru#");
        o.setNsPrefix("mom", "http://mom.ksu.ru#");
        o.setNsPrefix("miku", "http://miku.ksu.ru#");
        o.setNsPrefix("kgasu", "http://www.kgasu.ru#");
        o.setNsPrefix("benaki", "http://www.benaki.ru#");
        o.setNsPrefix("culthes", "http://thesaurus.niimm.edu.ru#");
    }

    private boolean isProblemDescriptor(Resource i)
    {
        // стоящие в условии понятия исключены из индекса по следующим причинам
        return
                // 1. вызывают ошибку при сериализации соответствующей онтологии (индекса)
                i.getLocalName().equalsIgnoreCase("aatnum_300191091")
                        || i.getLocalName().equalsIgnoreCase("aatnum_300191088")
                        || i.getLocalName().equalsIgnoreCase("aatnum_300191087")
                        || i.getLocalName().equalsIgnoreCase("aatnum_300179869")
                        || i.getLocalName().equalsIgnoreCase("aatnum_300122241")
                        || i.getLocalName().equalsIgnoreCase("aatnum_300004795")
                        || i.getLocalName().equalsIgnoreCase("aatnum_300004789")
                        || i.getLocalName().equalsIgnoreCase("aatnum_300004894")
                        || i.getLocalName().equalsIgnoreCase("aatnum_300010357")


                        // 2. вызвыают StackOverflow, т.к. находятся на вершине (или близко) иерархии
                        || i.getLocalName().equalsIgnoreCase("aatnum_300264550")
                        || i.getLocalName().equalsIgnoreCase("aatnum_300264551")
                        || i.getLocalName().equalsIgnoreCase("aatnum_300264552")
                        || i.getLocalName().equalsIgnoreCase("aatnum_300264092")
                        || i.getLocalName().equalsIgnoreCase("aatnum_300264091")
                        || i.getLocalName().equalsIgnoreCase("aatnum_300264089")
                        || i.getLocalName().equalsIgnoreCase("aatnum_300264090")
                        || i.getLocalName().equalsIgnoreCase("aatnum_300264087")
                        || i.getLocalName().equalsIgnoreCase("aatnum_300264088")
                        || i.getLocalName().equalsIgnoreCase("aatnum_300000000")

                        // 3. долго работает... )
                        || i.getLocalName().equalsIgnoreCase("aatnum_300241490");
    }

    private boolean isInIndex(Resource i)
    {
        File f = new File("TMP/index/" + i.getLocalName() + ".owl");
        return f.exists();
    }

    private boolean isNarrowTermInIndex(Resource i, ObjectProperty P127F_)
    {
        File f = new File("TMP/index/" + ((Resource)i.getProperty(P127F_).getObject()).getLocalName() + ".owl");
        return f.exists();
    }

    private boolean isEmptyModel(OntModel res)
    {
        StmtIterator allStmt = res.listStatements();
        for(Object i : allStmt.toList())
        {
            Statement j = (Statement)i;
            if(j.getObject().isResource())
            {
                if(!((Resource)j.getObject()).getURI().startsWith(SearchConstants.rusaatNS))
                {
                    return false;
                }
            }
            if(!j.getSubject().getURI().startsWith(SearchConstants.rusaatNS))
            {
                return false;
            }
        }
        return true;
    }

    // -------------------------------------------------------------------------------------

    public OntModel RetrieveModelFromIndex(Resource i)
    {
        OntModel o = ModelFactory.createOntologyModel(OntModelSpec.OWL_LITE_MEM);
        try {
            File f = new File("TMP/index/" + i.getLocalName() + ".owl");
            if(f.exists())
            {
            o.read(new FileReader(f), f.toURL().toString(), "RDF/XML-ABBREV");
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        return o;
    }
}
